import KaseoCanvas2dGraphicsDI, { KaseoCommon2dGDI } from "./dev/KaseoCanvasGraphicsDI";
import KaseoKeyboardDI, { KaseoCommonKDI } from "./dev/KaseoKeyboardDI";
import { PROGRAM_ON_BOOT } from "./KaseoConfigs";
import KaseoDevice from "./KaseoDevice";
import KaseoDeviceInterface from "./KaseoDeviceInterface";
import KaseoProgram from "./KaseoProgram";


export interface KaseoRuntimeBridge {
    exit: (self: KaseoProgram) => void
    
    getMajorKeyboardDI: () => KaseoCommonKDI | null
    getMajorGraphicsGDI: () => KaseoCommon2dGDI | null
    getExtraGraphicsGDI: () => KaseoCommon2dGDI | null

    getKnockDevType: () => symbol
}


export default class KaseoRuntime {
    private registeredDevices: KaseoDevice[] = []

    private _majorKeyboardDI: KaseoKeyboardDI | null = null
    private _majorGraphicsDI: KaseoCanvas2dGraphicsDI | null = null
    private _extraGraphicsDI: KaseoCanvas2dGraphicsDI | null = null

    private runningProgram: KaseoProgram | null = null
    private programWillExit: boolean = false

    private lastKnockDevType: symbol = Symbol('<NO DEV>')

    private readonly bridge: KaseoRuntimeBridge = {
        exit: (self: KaseoProgram) => {
            if (this.runningProgram !== self)
                return
            this.programWillExit = true
        },
        getMajorKeyboardDI: () => this._majorKeyboardDI,
        getMajorGraphicsGDI: () => this._majorGraphicsDI,
        getExtraGraphicsGDI: () => this._extraGraphicsDI,
        getKnockDevType: () => this.lastKnockDevType
    }

    constructor() {

    }

    registerMajorKeyboardDI(kdi: KaseoKeyboardDI) {
        console.log('Kaseo-Runtime: major KDI registered')

        this._majorKeyboardDI = kdi
    }

    registerMajorGraphicsDI(gdi: KaseoCanvas2dGraphicsDI) {
        console.log('Kaseo-Runtime: major GDI registered')

        this._majorGraphicsDI = gdi
    }

    registerExtraMonitorDI(gdi: KaseoCanvas2dGraphicsDI) {
        console.log('Kaseo-Runtime: extra GDI registered')

        this._extraGraphicsDI = gdi
    }

    registerDevice(devInterface: KaseoDeviceInterface): number {
        const dev = new KaseoDevice(devInterface, (devType: symbol) => this.$$knock(devType))
        const idx = this.registeredDevices.length

        this.registeredDevices.push(dev)

        console.log('Kaseo-Runtime: DI registered:', devInterface.$$devname)

        devInterface.__$$devAdaptor = dev

        return idx
    }

    execProgram(program: KaseoProgram) {
        this.runningProgram?.onExit(this.bridge)

        this.runningProgram = program
        this.runningProgram.onLaunch(this.bridge)
    }


    $$boot() {
        console.log('Kaseo-Runtime: boot')

        for (let i = 0; i < this.registeredDevices.length; ++i) {
            this.registeredDevices[i].devInterface.$$devSetup()
        }

        this.execProgram(new PROGRAM_ON_BOOT())

        requestAnimationFrame((ts) => this.$$tick(ts))
    }

    $$knock(devType: symbol) {
        this.programWillExit = false
        this.lastKnockDevType = devType

        for (let i = 0; i < this.registeredDevices.length; ++i) {
            this.registeredDevices[i].devInterface.$$devTickBefore()
        }

        this.runningProgram?.onKnock(this.bridge)

        for (let i = 0; i < this.registeredDevices.length; ++i) {
            this.registeredDevices[i].devInterface.$$devTickAfter()
        }

        if (this.programWillExit) {
            this.runningProgram?.onExit(this.bridge)
            this.runningProgram = null
        }
    }

    $$tick(timestamp: number) {
        this.programWillExit = false

        for (let i = 0; i < this.registeredDevices.length; ++i) {
            this.registeredDevices[i].devInterface.$$devTickBefore()
        }

        this.runningProgram?.onUpdate(this.bridge)

        for (let i = 0; i < this.registeredDevices.length; ++i) {
            this.registeredDevices[i].devInterface.$$devTickAfter()
        }

        if (this.programWillExit) {
            this.runningProgram?.onExit(this.bridge)
            this.runningProgram = null
        }

        requestAnimationFrame((ts) => this.$$tick(ts))
    }
}
